use std::collections::HashSet;

use ndarray::{array, Array2};
use rstest::rstest;

use crate::tests::fixtures::{
    rand_float,
    rand_string,
    rand_usize,
    random_missing_tile_policy,
    random_tile_selection_policy,
    random_tile_text,
    random_tile_type,
    RANDOM_USIZE,
};
use crate::tiling::slot_grid::tests::create_reproducible_grid_slot_with_tile_id_no_excl_connectors;
use crate::tiling::tiles::tile_list::TileList;
use crate::tiling::tiles::{ArrayInitializable, Connectors, TileConnection, TileConnectionArray};
use crate::tiling::{GridSlot, SlotGrid, VacancyPolicy};

// noinspection DuplicatedCode
#[rstest]
// region avoid vacancies, no vacant neighbors, success
#[case::avoid_vacancies_no_vacant_neighbors_success(
    TileConnection {
        tiles: vec![1, *RANDOM_USIZE, 4],
        weights: vec![rand_float(), rand_float() + 2.0, rand_float()],
    },
    2,
    1,
    array![
        [None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(*RANDOM_USIZE + 4)),
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(*RANDOM_USIZE + 9)),
        ],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None),
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(*RANDOM_USIZE * 2)),
        ],
    ],
    VacancyPolicy::Avoid,
    Some(*RANDOM_USIZE),
    Some(HashSet::new()),
)]
// endregion
// region avoid vacancies, four vacant neighbors, success
#[case::avoid_vacancies_four_vacant_neighbors_success(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    Some(2),
    Some(HashSet::new()),
)]
// endregion
// region avoid vacancies, three vacant neighbors, success
#[case::avoid_vacancies_three_vacant_neighbors_success(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    3,
    4,
    array![
        [None, None, None, None, None, None],
        [None, None, None, None, None, None],
        [
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [
            None,
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None),
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(6)),
        ],
        [
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    Some(2),
    Some(HashSet::new()),
)]
// endregion
// region avoid vacancies, two vacant neighbors, success
#[case::avoid_vacancies_two_vacant_neighbors_success(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            17,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    1,
    1,
    array![
        [None, None, None],
        [None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4))],
    ],
    VacancyPolicy::Avoid,
    Some(17),
    Some(HashSet::new()),
)]
// endregion
// region avoid vacancies, southern/eastern vacancies do not fit to new tile, failure
#[case::avoid_vacancies_southern_eastern_vacancies_do_not_fit_to_new_tile_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    1,
    1,
    array![
        [None, None, None],
        [None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4))],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for northern vacancy, failure
#[case::avoid_vacancies_no_fitting_tile_for_northern_vacancy_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(10)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for northern vacancy, large, failure
#[case::avoid_vacancies_no_fitting_tile_for_northern_vacancy_large_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    3,
    4,
    array![
        [None, None, None, None, None, None],
        [None, None, None, None, None, None],
        [
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(10)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [
            None,
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None),
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(6)),
        ],
        [
            None,
            None,
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for eastern vacancy, failure
#[case::avoid_vacancies_no_fitting_tile_for_eastern_vacancy_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(11)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for southern vacancy, failure
#[case::avoid_vacancies_no_fitting_tile_for_southern_vacancy_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(12)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for southern vacancy, alternative, failure
#[case::avoid_vacancies_no_fitting_tile_for_southern_vacancy_alternative_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float() + 3.6 ,
            rand_float(),
            rand_float() * 3.0,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(12)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for western vacancy, failure
#[case::avoid_vacancies_no_fitting_tile_for_western_vacancy_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(13)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 1, east: 3, south: 2, west: 4 }])),
)]
// endregion
// region avoid vacancies, no fitting tile for western vacancy, alternative, failure
#[case::avoid_vacancies_no_fitting_tile_for_western_vacancy_alternative_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            5,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(13)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    Some(HashSet::from([Connectors { north: 4, east: 5, south: 5, west: 6 }])),
)]
// endregion
// region ignore vacancies, success
#[case::ignore_vacancies_success(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            *RANDOM_USIZE + 1,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    1,
    1,
    array![
        [None, None, None],
        [None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4))],
    ],
    VacancyPolicy::Ignore,
    Some(*RANDOM_USIZE + 1),
    Some(HashSet::new()),
)]
// endregion
// region ignore vacancies, large, success
#[case::ignore_vacancies_large_success(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            *RANDOM_USIZE + 1,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(10)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(0)),
        ],
        [None, None, create_reproducible_grid_slot_with_tile_id_no_excl_connectors(None), None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Ignore,
    Some(*RANDOM_USIZE + 1),
    Some(HashSet::new()),
)]
// endregion
// region invalid current_slot, failure
#[case::invalid_current_slot_failure(
    TileConnection {
        tiles: vec![
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            rand_usize(0, 10),
            2,
            rand_usize(0, 10),
            rand_usize(0, 10),
        ],
        weights: vec![
            rand_float(),
            rand_float(),
            rand_float(),
            rand_float() * 3.0,
            rand_float() + 3.6 ,
            rand_float(),
            rand_float(),
        ],
    },
    2,
    2,
    array![
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(1)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(11)),
        ],
        [None, None, None, None],
        [
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(3)),
            None,
            create_reproducible_grid_slot_with_tile_id_no_excl_connectors(Some(4)),
        ],
    ],
    VacancyPolicy::Avoid,
    None,
    None,
)]
// endregion
fn test_choose_tile(
    #[case] tile_connection: TileConnection,
    #[case] row: usize,
    #[case] col: usize,
    #[case] mut grid: Array2<Option<GridSlot>>,
    #[case] vacancy_policy: VacancyPolicy,
    #[case] expected_result: Option<usize>,
    #[case] expected_excluded_connectors: Option<HashSet<Connectors>>,
) {
    let tile_list = TileList {
        tiles: vec![
            // 0
            random_tile_text(Connectors {
                north: 2,
                east: 2,
                south: 3,
                west: 2,
            }),
            // 1
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 2,
                east: 1,
                south: 1,
                west: 1,
            }),
            // 2
            random_tile_text(Connectors {
                north: 1,
                east: 3,
                south: 2,
                west: 4,
            }),
            // 3
            random_tile_text(Connectors {
                north: 1,
                east: 1,
                south: 3,
                west: 1,
            }),
            // 4
            random_tile_text(Connectors {
                north: 1,
                east: 1,
                south: 1,
                west: 4,
            }),
            // 5, border tile
            random_tile_text(Connectors {
                north: 4,
                east: 5,
                south: 5,
                west: 6,
            }),
            // 6
            random_tile_text(Connectors {
                north: 5,
                east: 2,
                south: 1,
                west: 1,
            }),
            // 7
            random_tile_text(Connectors {
                north: 1,
                east: 4,
                south: 1,
                west: 5,
            }),
            // 8
            random_tile_text(Connectors {
                north: 3,
                east: 6,
                south: 1,
                west: 3,
            }),
            // 9
            random_tile_text(Connectors {
                north: 2,
                east: 4,
                south: 4,
                west: 1,
            }),
            // 10
            random_tile_text(Connectors {
                north: *RANDOM_USIZE + 2,
                east: 5,
                south: 1,
                west: 1,
            }),
            // 11
            random_tile_text(Connectors {
                north: 2,
                east: 2,
                south: 4,
                west: 2,
            }),
            // 12
            random_tile_text(Connectors {
                north: 1,
                east: 6,
                south: 3,
                west: 1,
            }),
            // 13
            random_tile_text(Connectors {
                north: 2,
                east: 1,
                south: 3,
                west: 1,
            }),
            // 14
            random_tile_text(Connectors {
                north: 5,
                east: 6,
                south: 1,
                west: 5,
            }),
            // 15
            random_tile_text(Connectors {
                north: 6,
                east: 4,
                south: 4,
                west: 5,
            }),
            // 16
            random_tile_text(Connectors {
                north: 1,
                east: 5,
                south: 2,
                west: 4,
            }),
            // 17
            random_tile_text(Connectors {
                north: 1,
                east: 5,
                south: 6,
                west: 4,
            }),
        ],
        horizontal_connector_names: { (0..17).map(|_| rand_string(rand_usize(0, 8))).collect() },
        vertical_connector_names: { (0..17).map(|_| rand_string(rand_usize(0, 8))).collect() },
        tile_type: random_tile_type(),
    };
    let mut slot_grid = SlotGrid {
        num_rows: grid.nrows() - 1,
        num_cols: grid.ncols() - 1,
        grid: grid.clone(),
        border_tile_id: Some(5),
        tile_connections: TileConnectionArray::new(&tile_list.tiles),
        tile_list,
        missing_tile_policy: random_missing_tile_policy(),
        tile_selection_policy: random_tile_selection_policy(),
        vacancy_policy,
        current_slot: (row, col),
        current_predecessor: Some((*RANDOM_USIZE + 14, 5 * *RANDOM_USIZE + 2)),
        last_leaves: vec![],
        exposed_edge_count: rand_usize(1, 15),
    };

    let result = slot_grid.choose_tile(tile_connection);

    assert_eq!(result, expected_result);

    if let Some(expected_excluded_connectors) = expected_excluded_connectors {
        grid[[row, col]]
            .as_mut()
            .expect("the slot in grid at the given position should be Some(GridSlot)")
            .excluded_connectors = expected_excluded_connectors.clone();
        assert_eq!(
            slot_grid.grid[[row, col]]
                .as_ref()
                .expect("the slot in slot_grid.grid at the given position should be Some(GridSlot)")
                .excluded_connectors,
            expected_excluded_connectors,
        );
    }
    assert_eq!(slot_grid.grid, grid);
}
